package tk.mynoteapp.dev.demo02.lockgranularity;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * 加锁要考虑锁的粒度和场景问题
 *  - 不要滥用synchronized
 *      一是，没必要。通常情况下 60% 的业务代码是三层架构，数据经过无状态的 Controller、Service、Repository 流转到数据库，
 *      没必要使用 synchronized 来保护什么数据。
 *      二是，可能会极大地降低性能。使用 Spring 框架时，默认情况下 Controller、Service、Repository 是单例的，加上
 *      synchronized 会导致整个程序几乎就只能支持单线程，造成极大的性能问题。
 *
 *  - 即使我们确实有一些共享资源需要保护，也要尽可能降低锁的粒度，仅对必要的代码块甚至是需要保护的资源本身加锁。
 *
 *  - 如果精细化考虑了锁应用范围后，性能还无法满足需求的话，我们就要考虑另一个维度的粒度问题了，
 *    即：区分读写场景以及资源的访问冲突，考虑使用悲观方式的锁还是乐观方式的锁。
 *    根据需求判断是否优化：
 *    > 对于读写比例差异明显的场景，考虑使用 ReentrantReadWriteLock 细化区分读写锁，来提高性能。
 *    > 如果你的 JDK 版本高于 1.8、共享资源的冲突概率也没那么大的话，考虑使用 StampedLock 的乐观读的特性，进一步提高性能。
 *    > JDK 里 ReentrantLock 和 ReentrantReadWriteLock 都提供了公平锁的版本，在没有明确需求的情况下不要轻易开启公平锁特性，在任务很轻的情况下开启公平锁可能会让性能下降上百倍。
 */
@RestController
@RequestMapping("lockgranularity")
public class LockGranularityController {
    private Logger logger = LoggerFactory.getLogger(LockGranularityController.class);
    // 共享资源
    private List<Integer> data = new ArrayList<>();

    // 不涉及共享资源的慢方法
    private void slow() {
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
        }
    }

    // 错误的加锁方法
    @GetMapping("wrong")
    public int wrong() {
        long begin = System.currentTimeMillis();
        IntStream.rangeClosed(1, 1000).parallel().forEach(i -> {
            // 锁的粒度太粗
            synchronized (this) {
                slow();
                data.add(i);
            }
        });
        logger.info("wrong took: {}", System.currentTimeMillis() - begin);
        // 耗时：wrong took: 10494
        return data.size();
    }
    // 正确的加锁方法
    @GetMapping("right")
    public int right() {
        long begin = System.currentTimeMillis();
        IntStream.rangeClosed(1, 1000).parallel().forEach(i -> {
            slow();
            // 只对list加锁
            synchronized (this) {
                data.add(i);
            }
        });
        logger.info("right took: {}", System.currentTimeMillis() - begin);
        // 耗时：right took: 1329
        return data.size();
    }
}
